﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Data;
using System.Reflection;
using AliCMS.SystemModule;

namespace AliCMS.TemplateEngine
{
    internal class Get : Tag
    {
        internal Get(Engine engine, int index, int length, int contentIndex, int contentLength, Tag parent)
            : base(engine, index, length, contentIndex, contentLength, parent)
        {
        }

        internal override void Init()
        {
            Regex re = null;
            Match match = null;

            #region id
            _id = Id;
            if (String.IsNullOrEmpty(_id))
                _id = UniqueName + "ID";
            #endregion

            #region Source
            re = new Regex("Source=\"(?<v>.*?)\"", RegexOptions.Compiled);
            match = re.Match(Engine.Text, Index, Length);
            if (match.Success)
                _source = Engine.CreateExpression(this, match.Groups["v"].Value);
            #endregion

            if (_source == null)
            {
                #region Mark
                _mark = String.Empty;
                re = new Regex("Mark=\"(?<v>.*?)\"", RegexOptions.Compiled);
                match = re.Match(Engine.Text, Index, Length);
                if (match.Success)
                    _mark = match.Groups["v"].Value;
                #endregion

                #region ModuleID, ModuleName
                _module = Engine.GetModuleByMark(_mark);
                if (_module == null)
                {
                    Exception exp = new Exception("lang_parse_template_cant_find_module_for_mark");
                    Engine.ReportError(exp, match.Index);
                }
                #endregion

                #region Object
                re = new Regex("Object=\"(?<v>.*?)\"", RegexOptions.Compiled);
                match = re.Match(Engine.Text, Index, Length);
                if (!match.Success)
                {
                    Exception exp = new Exception("lang_parse_template_object_must_set");
                    Engine.ReportError(exp, Index);
                }

                string objectName = match.Groups["v"].Value;

                DataRow[] rows = Manager.Objects.Select(Alias.Module.Object.Namespace + "='" + _module[Alias.Module.Namespace] + "' and FullName='" + objectName + "'");
                if (rows.Length == 0)
                {
                    Exception exp = new Exception("lang_parse_template_module_not_include_object");
                    exp.Data.Add(3, _module[Alias.Module.Name]);
                    exp.Data.Add(4, objectName);
                    Engine.ReportError(exp, Index);
                }

                _object = rows[0];

                #region InterfaceName
                _interfaceName = String.Empty;
                int dot = objectName.LastIndexOf('.');
                if (dot != -1)
                    _interfaceName = objectName.Insert(dot + 1, "I");
                else
                    _interfaceName = "I" + objectName;
                #endregion
                #endregion

                #region Operation
                re = new Regex("Operation=\"(?<v>.*?)\"", RegexOptions.Compiled);
                match = re.Match(Engine.Text, Index, Length);
                if (match.Success)
                {
                    _operation = Engine.CreateExpression(this, match.Groups["v"].Value);
                    string functionName = _operation.Value;
                    int paramaterCount = 1;
                    if (_operation.Type == ExpressionType.Function)
                    {
                        functionName = _operation.FunctionName;
                        paramaterCount = _operation.Params.Count;
                    }

                    string typeName = _module[Alias.Module.Namespace] + "." + _interfaceName;
                    Type type = Type.GetType(typeName);
                    if (type == null)
                    {
                        Assembly assembly = null;
                        //if (String.Equals(_module[Alias.Module.Namespace], "AliCMS.SystemModule"))
                        //    assembly = Assembly.Load("AliCMS.Base");
                        //else
                            assembly = Assembly.Load(_module[Alias.Module.Namespace] + ".Interface");
                        type = assembly.GetType(typeName);
                    }
                    MethodInfo mi = type.GetMethod(functionName);
                    if (mi == null)
                    {
                        Type[] types = type.GetInterfaces();
                        foreach (Type t in types)
                        {
                            mi = t.GetMethod(functionName);
                            if (mi != null)
                                break;
                        }

                        if (mi == null)
                        {
                            Exception exp = new Exception("lang_parse_template_object_not_include_operation");
                            exp.Data.Add(3, _object["FullName"].ToString());
                            exp.Data.Add(4, functionName);
                            Engine.ReportError(exp, match.Groups["v"].Index);
                        }
                    }

                    _returnType = GetTypeName(mi.ReturnType);

                    ParameterInfo[] _pi = mi.GetParameters();
                    if (paramaterCount != _pi.Length)
                    {
                        Exception exp = new Exception("lang_parse_template_params_count_not_correct");
                        exp.Data.Add(3, functionName);
                        Engine.ReportError(exp, match.Groups["v"].Index);
                    }
                }
                else
                {
                    _returnType = "Dictionary<string, object>";
                }
                #endregion

                #region Lang
                re = new Regex("Lang=\"(?<v>.*?)\"", RegexOptions.Compiled);
                match = re.Match(Engine.Text, Index, Length);
                if (match.Success)
                {
                    _lang = match.Groups["v"].Value;
                }
                #endregion

                #region ForMeta
                re = new Regex("ForMeta=\"(?<v>true|false)\"", RegexOptions.Compiled | RegexOptions.IgnoreCase);
                match = re.Match(Engine.Text, Index, Length);
                if (match.Success)
                    _forMeta = Convert.ToBoolean(match.Groups["v"].Value);
                else
                    _forMeta = false;
                #endregion
            }
        }
        internal override void Parse()
        {
            ParseToCode();
            ParseToXml();
        }
        internal override string ParseVariable(Expression variable)
        {
            if (variable.Parent != null && variable.Parent.Right == variable && variable.Parent.Operator == "." || 
                variable.Parent != null && variable.Parent.Left == variable && variable.Parent.Parent != null && variable.Parent.Parent.Right == variable.Parent && variable.Parent.Parent.Operator == "." ||
                _object == null)
                return null;

            StringBuilder result = null;
            MatchCollection matches = Regex.Matches(variable.Value, @"((?<id>[a-zA-Z_]\w*)\[(?<name>[a-zA-Z_]\w*)\])|((?<id>[a-zA-Z_]\w*)\[(?<name>""[a-zA-Z_]\w*"")\])|(\[(?<name>[a-zA-Z_]\w*)\])|(\[(?<name>""[a-zA-Z_]\w*"")\])|([a-zA-Z_]\w*)");
            for (int i = matches.Count - 1; i >= 0; i--)
            {
                Match match = matches[i];

                string value = String.Empty;
                if (match.Groups["id"].Success)
                {
                    if (match.Groups["id"].Value != Id)
                        continue;

                    value = match.Groups["name"].Value;
                }
                else if (match.Groups["name"].Success)
                {
                    value = match.Groups["name"].Value;
                }
                else
                {
                    value = match.Value;
                }

                DataRow[] rows = Manager.Properties.Select(Alias.Module.Object.Property.ObjectID + "='" + _object[Alias.Module.Object.ID] + "' and " + Alias.Module.Object.Property.Name + "='" + value + "'");
                if (rows.Length == 0)
                    continue;

                if (result == null)
                    result = new StringBuilder(variable.Value);

                result.Remove(match.Index, match.Length);
                string alias = _module[Alias.Module.Namespace] + ".Alias." + _object["FullName"] + "." + value;
                result.Insert(match.Index, "(" + _id + "==null?null:" + _id + "[" + alias + "])");
            }

            return result == null ? null : result.ToString();
        }
        internal override string ParseFunction(Expression function)
        {
            if (function.FunctionName != "BuildUrl" || function.Params.Count > 1 || _object == null)
                return null;

            if (String.IsNullOrEmpty(_mark) || Regex.IsMatch(_mark, @"\{?[a-fA-F0-9]{8}(-?[a-fA-F0-9]{4}){3}-?[a-fA-F0-9]{12}\}?"))
                function.InsertParam(0, "Channel", true);
            else
                function.InsertParam(0, Engine.ParseChannel(_mark), true);

            #region 判断和获取pageTemplate, pageID, PageUrl
            string objectName = _object["FullName"].ToString();
            string pageTemplate = null;
            string pageID = null;
            string pageUrl = null;
            string innerID = null;
            DataRow[] properties = Manager.Properties.Select(Alias.Module.Object.Property.ObjectID + "='" + _object[Alias.Module.Object.ID] + "'");
            foreach (DataRow property in properties)
            {
                string name = property[Alias.Module.Object.Property.Name].ToString();
                string id = property[Alias.Module.Object.Property.UniqueInnerID].ToString();

                if (name == "PageTemplate")
                    pageTemplate = id;
                else if (name == "PageID")
                    pageID = id;
                else if (name == "PageUrl")
                    pageUrl = id;
                else if (name == "InnerID")
                    innerID = id;
            }

            #region 处理交叉表情况
            if (pageID == null && objectName.IndexOf('.') != -1)
            {
                string prefix = objectName.Substring(objectName.LastIndexOf('.') + 1);

                foreach (DataRow property in properties)
                {
                    string name = property[Alias.Module.Object.Property.Name].ToString();
                    string id = property[Alias.Module.Object.Property.UniqueInnerID].ToString();

                    if (name == prefix + "PageTemplate")
                        pageTemplate = id;
                    else if (name == prefix + "PageID")
                        pageID = id;
                    else if (name == prefix + "PageUrl")
                        pageUrl = id;
                }
            }
            #endregion
            #endregion

            #region PathInTemplate
            if (function.Params.Count == 1)
            {
                string pathInTemplate = null;
                for (int i = 0; i < objectName.Length; i++)
                {
                    if (objectName[i] == '.')
                    {
                        pathInTemplate += "/";
                    }
                    else
                    {
                        if (i > 0 && Char.IsUpper(objectName[i]) && objectName[i - 1] != '.')
                            pathInTemplate += "_";
                        pathInTemplate += Char.ToLower(objectName[i]);
                    }
                }

                pathInTemplate = "\"" + pathInTemplate + "\"";
                function.InsertParam(-1, pathInTemplate, true);
            }
            #endregion

            #region Header
            string header = null;

            if (String.IsNullOrEmpty(pageID) && !String.IsNullOrEmpty(innerID))
                header = "\"ID=\" + " + _id + "[\"" + innerID + "\"]";
            else if (!String.IsNullOrEmpty(pageID))
                header = "\"ID=\" + " + _id + "[\"" + pageID + "\"]";
            else
                header = "\"\"";

            if (!String.IsNullOrEmpty(pageTemplate))
                header += " + \"&PageTemplate=\" + " + _id + "[\"" + pageTemplate + "\"]";

            if (!String.IsNullOrEmpty(pageUrl))
                header += " + \"&PageUrl=\" + HttpUtility.UrlEncode(" + _id + "[\"" + pageUrl + "\"].ToString())";

            function.InsertParam(-1, header, true);
            #endregion

            return function.FunctionName;
        }

        private void ParseToCode()
        {
            StringBuilder result = new StringBuilder();

            if (_source == null)
            {
                Regex re = null;
                Match match = null;

                #region InstanceID
                string instanceID = "int instanceID;\r\n";
                if (String.IsNullOrEmpty(_mark))
                {
					if (Engine.IsMultiSite)
						instanceID += "instanceID = SubInstanceID;";
					else
						instanceID += "instanceID = InstanceID;";
                }
                #region 当Mark为Guid时，表示Instance的ID。主要用在“应用”上。通过可视编辑的方式选择自己的应用，对应的实例ID放在Mark里。
                else if (Regex.IsMatch(_mark, @"\{?[a-fA-F0-9]{8}(-?[a-fA-F0-9]{4}){3}-?[a-fA-F0-9]{12}\}?"))
                {
                    instanceID += "instanceID = " + Manager.Instances.Select(Alias.Instance.ID + "='" + (new Guid(_mark)) + "'")[0][Alias.Instance.InnerID] + ";";
                }
                #endregion
                else
                {
					if (Engine.IsMultiSite)
						instanceID += "instanceID = (int)Manager.Instances.Select(AliCMS.SystemModule.Alias.Instance.ID + \"='\" + GetBindingInstanceID(" + Engine.ParseChannel(_mark) + ") + \"'\")[0][AliCMS.SystemModule.Alias.Instance.InnerID];";
					else
						instanceID += "instanceID = (int)" + Engine.ParseChannel(_mark) + "[AliCMS.SystemModule.Alias.Instance.InnerID];";
				}
                #endregion

                #region Guid和InnerID
                string id = String.Empty;
                if (_source == null && _operation == null)
                {
                    re = new Regex("(Guid=\"(?<g>.*?)\")|(InnerID=\"(?<i>.*?)\")|(PageID=\"(?<p>.*?)\")", RegexOptions.Compiled);
                    match = re.Match(Engine.Text, Index, Length);
                    if (match.Success)
                    {
                        if (match.Groups["i"].Success)
                        {
                            string innerID = match.Groups["i"].Value;
                            id = "id = Convert.ToInt32(" + Engine.ParseExpression(this.Parent == null ? this : this.Parent, innerID) + ");";
                        }
                        else if (match.Groups["p"].Success)
                        {
                            string pageId = match.Groups["p"].Value;
                            //DataRow[] rows = Manager.Properties.Select(Alias.Module.Object.Property.ObjectID + "='" + _object[Alias.Module.Object.ID] + "' and " + Alias.Module.Object.Property.Name + "='PageID'");
                            //if (rows.Length > 0)
                                id = "id = Convert.ToString(" + Engine.ParseExpression(this.Parent == null ? this : this.Parent, pageId) + ");";
                            //else
                            //    id = "id = Convert.ToInt32(" + Engine.ParseExpression(this.Parent == null ? this : this.Parent, pageID) + ");";
                        }
                        else
                        {
                            string guid = match.Groups["g"].Value;
                            if (Regex.IsMatch(guid, @"\{?[a-fA-F0-9]{8}(-?[a-fA-F0-9]{4}){3}-?[a-fA-F0-9]{12}\}?"))
                            {
                                id = "id = new Guid(Convert.ToString(\"" + guid + "\"));";
                            }
                            else
                            {
                                id = "object o = " + Engine.ParseExpression(this.Parent == null ? this : this.Parent, guid) + ";\r\n";
                                id += @"
                                    if (o is Guid)
                                        id = (Guid)o;
                                    else
                                        id = new Guid(Convert.ToString(o));
                                    ";
                            }
                        }
                    }
                }
                #endregion

                result.Append(@"
                            protected <%ReturnType%> <%Name%>
                            {
                                get
                                {
                                    object id = null;
                                    <%ID%>
                                    if (_<%Name%> != null)
                                        return (<%ReturnType%>)_<%Name%>;

                                    <%InstanceID%>

                                    using (<%Namespace%>.<%Interface%> bll = (<%Namespace%>.<%Interface%>)Manager.CreateObject(""<%Object%>"", instanceID, <%Lang%>, <%Validate%>))
                                    {
                                        _<%Name%> = bll.<%Operation%>;
                                    }

                                    return (<%ReturnType%>)_<%Name%>;
                                }
                            }
                            private object _<%Name%>;
                            ");
                if (_operation == null)
                {
                    result.Replace("<%Operation%>", "Get(id)");
                }
                else
                {
                    result.Replace("<%Operation%>", _operation.Parse());
                }
                result.Replace("<%Name%>", _id);
                result.Replace("<%Namespace%>", _module[Alias.Module.Namespace].ToString());
                result.Replace("<%Interface%>", _interfaceName);
                result.Replace("<%Object%>", _object["FullName"].ToString());
                result.Replace("<%InstanceID%>", instanceID);
                result.Replace("<%Validate%>", Validate);
                result.Replace("<%ID%>", id);
                result.Replace("<%ReturnType%>", _returnType);
                if (String.IsNullOrEmpty(_lang))
                {
                    if (Engine.IsAdmin)
                        result.Replace("<%Lang%>", "ContentLang");
                    else
                        result.Replace("<%Lang%>", "PageLang");
                }
                else
                {
                    result.Replace("<%Lang%>", Engine.ParseExpression(this, _lang));
                }

                if (ChildTags.Count > 0)
                {
                    StringBuilder replacement = new StringBuilder();
                    foreach (Tag tag in ChildTags)
                    {
                        if (tag is Code)
                        {
                            replacement.Append(tag.ResultCode);
                        }
                        else if (tag is OutputStatment)
                        {
                            replacement.Append("object " + tag.UniqueName + " = " + tag.ResultCode + ";\r\n");
                            replacement.Append("result.Replace(\"{#" + tag.UniqueName + "#}\"," + tag.UniqueName + " == null ? \"\" : " + tag.UniqueName + ".ToString());\r\n");
                        }
                        else if (tag is IfStatment)
                        {
                            replacement.Append(tag.ResultCode);
                            replacement.Append("result.Replace(\"{#" + tag.UniqueName + "#}\"," + tag.UniqueName + ");\r\n");
                        }
                        else if (tag.ResultCode != null && Regex.IsMatch(tag.ResultCode, "protected string " + tag.UniqueName + @"\s+{\s+get\s+"))
                        {
                            replacement.Append("result.Replace(\"{#" + tag.UniqueName + "#}\"," + tag.UniqueName + ");\r\n");
                        }
                    }

                    result.Append(@"
                                protected string <%Name%>
                                {
                                    get
                                    {
                                        if (<%Name%>ID == null)
                                            return String.Empty;

                                        StringBuilder result = new StringBuilder(_xmlTemplate.SelectSingleNode(""/"" + ""/<%Name%>"").InnerText);
                                        <%Replacement%>
                                        return result.ToString();
                                    }
                                }
                              ");
                    result.Replace("<%Name%>", UniqueName);
                    result.Replace("<%Replacement%>", replacement.ToString());
                }
            }
            else
            {
                result.Append(@"
                            protected Dictionary<string, object> <%Name%>
                            {
                                get
                                {
                                    return <%Replacement%>;
                                }
                            }
                          ");
                result.Replace("<%Name%>", _id);
                result.Replace("<%Replacement%>", _source.Parse());
            }

            ResultCode = result.ToString();
        }
        private void ParseToXml()
        {
            if (ChildTags.Count == 0)
                return;

            StringBuilder result = new StringBuilder(Engine.Text.Substring(ContentIndex, ContentLength));
            for (int i = ChildTags.Count - 1; i >= 0; i--)
            {
                Tag tag = ChildTags[i];
                result.Remove(tag.Index - ContentIndex, tag.Length);
                if (tag is IfStatment || tag is OutputStatment || (tag.ResultCode != null && Regex.IsMatch(tag.ResultCode, "protected string " + tag.UniqueName + @"\s+{\s+get\s+")))
                    result.Insert(tag.Index - ContentIndex, "{#" + tag.UniqueName + "#}");
            }

            ResultXml = "<![CDATA[" + result.ToString() + "]]>";
        }

        private string GetTypeName(Type type)
        {
            Type nullableType = Nullable.GetUnderlyingType(type);
            if (nullableType != null)
                return nullableType.FullName + "?";

            if (!type.IsGenericType)
                return type.FullName;

            StringBuilder sb = new StringBuilder();
            foreach (Type t in type.GetGenericArguments())
            {
                if (sb.Length == 0)
                    sb.Append(type.FullName.Substring(0, type.FullName.IndexOf('`')) + "<");
                else
                    sb.Append(',');
                sb.Append(GetTypeName(t));
            }
            sb.Append('>');
            return sb.ToString();
        }

        #region Properties
        internal DataRow Module
        {
            get { return _module; }
        }
        internal string Object
        {
            get { return _object["FullName"].ToString(); }
        }
        internal bool ForMeta
        {
            get { return _forMeta; }
        }
        #endregion

        #region Fields
        private string _mark;
        private DataRow _module;
        private DataRow _object;
        private string _id;
        private string _interfaceName;
        private Expression _operation;
        private Expression _source;
        private string _returnType;
        private string _lang;
        private bool _forMeta;
        #endregion
    }
}
